package cn.flying.cloud.tool.code.service;

import javax.annotation.Resource;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.ibatis.annotations.Mapper;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.builder.CustomFile;
import com.baomidou.mybatisplus.generator.engine.VelocityTemplateEngine;
import com.baomidou.mybatisplus.generator.fill.Column;

import cn.flying.cloud.base.common.exception.BizException;
import cn.flying.cloud.base.common.utils.Ft;
import cn.flying.cloud.base.common.utils.lang.JdbcUtil;
import cn.flying.cloud.tool.code.entity.Field;
import cn.flying.cloud.tool.code.entity.Table;
import cn.flying.cloud.tool.scheme.entity.SchemeInfo;
import cn.flying.cloud.tool.scheme.service.SchemeInfoService;
import cn.flying.cloud.tool.vo.GenerateConfig;
import cn.flying.cloud.tool.vo.TableInfoVO;

/**
 * @author: admin
 * @date: 2024年03月26日 21:28
 * @version: 1.0
 */
@Service
public class GenerateCodeService {
    private static final Logger logger = LoggerFactory.getLogger(GenerateCodeService.class);

    private static final String jdbcUrl = "jdbc:mysql://%s:%s/%s?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&useAffectedRows=true";

    @Resource
    private SchemeInfoService schemeInfoService;

    /**
     * 查询所有表信息
     *
     * @param id
     * @return
     */
    public List<TableInfoVO> getTableList(Long id) throws Exception {
        SchemeInfo schemeInfo = schemeInfoService.getById(id);
        if (Objects.isNull(schemeInfo)) {
            throw new BizException("获取数据表失败！");
        }
        //数据库连接信息
        String url = String.format(jdbcUrl, schemeInfo.getHost(), schemeInfo.getPort(), schemeInfo.getName());
        String username = schemeInfo.getUsername();
        String password = schemeInfo.getPassword();
        Connection connection = JdbcUtil.getConnect(url, username, password);
        String sql = "select table_name,table_comment from information_schema.tables where table_schema = ?";
        List<TableInfoVO> list = JdbcUtil.executeQuery(connection, sql, TableInfoVO.class, schemeInfo.getName());
//        ResultSet resultSet = JdbcUtil.executeQuery(connection, sql, schemeInfo.getName());
//        List<TableInfoVO> list = new ArrayList<>();
//        TableInfoVO infoVo;
//        while (resultSet.next()) {
//            infoVo = new TableInfoVO();
//            infoVo.setTableName(resultSet.getString("table_name"));
//            infoVo.setTableComment(resultSet.getString("table_comment"));
//
//            list.add(infoVo);
//        }
        JdbcUtil.closeConnect();
        return list.stream().filter(t -> !t.getTableName().startsWith("qrtz") && !t.getTableName().startsWith("batch") && !t.getTableName().startsWith("act") && !t.getTableName().startsWith("flw")).collect(Collectors.toList());
    }

    /**
     * 生成代码，mybatis-plus风格
     *
     * @param config
     */
    public void generateMpStyleCode(GenerateConfig config) {
        //数据库连接信息
        String url = getUrl(config);
        String username = config.getUsername();
        String password = config.getPassword();

        //全局配置参数
        //作者
        String author = StringUtils.hasText(config.getAuthor()) ? config.getAuthor() : "system";
        //指定输出目录
        String outputDir = System.getProperty("user.dir");

        //包配置参数
        //父包名 如：cn.flying.cloud.system
        String parent = config.getParent();
        //父包模块名 如：user
        String moduleName = config.getModuleName();
        //Entity 实体类包名
        String entity = "entity";
        //Mapper 包名
        String mapper = "mapper";
        //Mapper XML 包名
        String mapperXml = moduleName.replace(".", "/") + "/";
        //Service 包名
        String service = "service";
        //Service Impl 包名
        String serviceImpl = "service.impl";
        //Controller 包名
        String controller = "controller";

        //要生成的数据库表
        List<String> tables = new ArrayList<>();
        if (Ft.String.isNotBlank(config.getTableName())) {
            tables.add(config.getTableName());
        } else {
            tables.addAll(config.getTableNames());
        }
        logger.info("模板代码生成的文件目录：【{}】", outputDir);

        FastAutoGenerator.create(url, username, password)
                //全局配置
                .globalConfig(builder -> {
                    builder.author(author)
                            .commentDate("yyyy-MM-dd HH:mm:ss")
                            .disableOpenDir()
                            .outputDir(outputDir + "/src/main/java");
                })
                //包的配置
                .packageConfig(builder -> {
                    // 设置父包名
                    builder.parent(parent)
                            // 设置父包模块名
                            .moduleName(moduleName).entity(entity).mapper(mapper).service(service).serviceImpl(serviceImpl).controller(controller)
                            // 设置mapperXml生成路径
                            .pathInfo(Collections.singletonMap(OutputFile.xml, outputDir + "/src/main/resources/mapper/" + mapperXml));
                })
                // 模板配置
                .templateConfig(builder -> builder.service("/tpl/mybatis-plus/Service.java.vm")
                        .serviceImpl("/tpl/mybatis-plus/ServiceImpl.java.vm")
                        .entity("/tpl/mybatis-plus/Entity.java.vm")
                        .mapper("/tpl/mybatis-plus/Mapper.java.vm")
                        .controller("/tpl/mybatis-plus/Controller.java.vm")
                        .xml("/tpl/mybatis-plus/Mapper.xml.vm")
                        .build())

                // 注入配置（自定义）
                .injectionConfig(builder -> {
                    Map<String, String> map = new HashMap<>();
                    map.put("api.js", "tpl/mybatis-plus/api.js.vm");
                    map.put("index.vue", "tpl/mybatis-plus/index.vue.vm");
                    map.put("detail.vue", "tpl/mybatis-plus/detail.vue.vm");

                    List<CustomFile> files = map.entrySet().stream().map(
                            (e) -> (new CustomFile.Builder()).fileName(e.getKey()).templatePath(e.getValue()).filePath(outputDir + "/src/front/").enableFileOverride().build()
                    ).collect(Collectors.toList());


                    files.add((new CustomFile.Builder()).fileName("Bo.java").templatePath("tpl/mybatis-plus/Bo.java.vm").filePath(outputDir + "/src/main/java/" + (parent + "." + moduleName).replaceAll("\\.", "/") + "/bo").enableFileOverride().build());

                    builder.beforeOutputFile((tableInfo, objMap) -> {
                        builder.customFile(files).build();
                    });
                })

                //策略配置
                .strategyConfig(builder -> {
                    // 设置需要生成的表名
                    builder.addInclude(tables)
                            //会去除掉生成的类的前缀
                            .addTablePrefix(Ft.String.isNotBlank(config.getPrefix()) ? config.getPrefix() : "")
                            .controllerBuilder()
                            .enableFileOverride()
                            .enableRestStyle()
                            .serviceBuilder()
                            .enableFileOverride()
                            .formatServiceFileName("%sService")
                            .formatServiceImplFileName("%sServiceImpl")
                            .mapperBuilder()
                            .enableFileOverride()
                            .mapperAnnotation(Mapper.class)
                            .enableBaseResultMap()
                            .entityBuilder()
                            // 使用雪花ID
                            .idType(IdType.ASSIGN_ID)
                            .enableFileOverride()
                            .enableTableFieldAnnotation()
                            // 逻辑删除字段
                            .logicDeleteColumnName("deleted")
                            // 乐观锁版本号
                            .versionColumnName("revision")
                            .addTableFills(
                                    new Column("created_time", FieldFill.INSERT),
                                    new Column("created_by", FieldFill.INSERT),
                                    new Column("updated_time", FieldFill.INSERT_UPDATE),
                                    new Column("updated_by", FieldFill.INSERT_UPDATE),
                                    new Column("tenantId", FieldFill.INSERT),
                                    new Column("deleted", FieldFill.INSERT),
                                    new Column("revision", FieldFill.INSERT)
                            )
                            .build();
                })
                //这里使用velocity的模板，Freemarker模板有点问题
                .templateEngine(new VelocityTemplateEngine() {
                    @Override
                    public void writer(Map<String, Object> objectMap, String templatePath, File outputFile) throws Exception {
                        if (templatePath.endsWith("Bo.java.vm")) {
                            String path = outputFile.getPath();
                            String substring = path.substring(0, path.lastIndexOf("\\") + 1);
                            String name = outputFile.getName();
                            String fileName = name.substring(0, name.length() - 10) + "Bo.java";
                            outputFile = new File(substring + fileName);
                        }
                        super.writer(objectMap, templatePath, outputFile);
                    }
                })
                .execute();

        // 重命名前端文件
        File folder = new File(outputDir + "/src/front/");
        File[] files = folder.listFiles();
        for (File file : files) {
            if (file.getName().endsWith("api.js")) {
                file.renameTo(new File(outputDir + "/src/front/" + "api.js"));
            }
            if (file.getName().endsWith("detail.vue")) {
                file.renameTo(new File(outputDir + "/src/front/" + "detail.vue"));
            }
            if (file.getName().endsWith("index.vue")) {
                file.renameTo(new File(outputDir + "/src/front/" + "index.vue"));
            }
        }
    }

    /**
     * 生成代码，mybatis风格
     *
     * @param config
     * @throws Exception
     */
    public void generateMbStyleCode(GenerateConfig config) throws Exception {
        //数据库连接信息
        String url = getUrl(config);
        String username = config.getUsername();
        String password = config.getPassword();

        //全局配置参数
        //作者
        String author = StringUtils.hasText(config.getAuthor()) ? config.getAuthor() : "system";
        //指定输出目录
        String outputDir = System.getProperty("user.dir");

        //包配置参数
        //父包名 如：cn.flying.cloud.system
        String parent = config.getParent();
        //父包模块名 如：user
        String moduleName = config.getModuleName();
        //Entity 实体类包名
        String entity;
        // Bo 包名
        String bo;
        //Mapper 包名
        String mapper;
        //Mapper XML 包名
        String mapperXml;
        //Service 包名
        String service;
        //Service Impl 包名
        String serviceImpl;
        //Controller 包名
        String controller;
        if (Ft.String.isNotBlank(moduleName)) {
            entity = parent + "." + moduleName + ".entity";
            bo = parent + "." + moduleName + ".bo";
            mapper = parent + "." + moduleName + ".mapper";
            service = parent + "." + moduleName + ".service";
            serviceImpl = parent + "." + moduleName + ".service.impl";
            controller = parent + "." + moduleName + ".controller";
            mapperXml = moduleName.replace(".", "/") + "/";
        } else {
            entity = parent + ".entity";
            bo = parent + ".bo";
            mapper = parent + ".mapper";
            service = parent + ".service";
            serviceImpl = parent + ".service.impl";
            controller = parent + ".controller";
            mapperXml = moduleName.replace(".", "/") + "/";
        }

        //要生成的数据库表
        List<String> tables = new ArrayList<>();
        tables.add(config.getTableName());

        Connection connection = JdbcUtil.getConnect(url, username, password);

        String tableQuerySql = "select table_name,table_comment from information_schema.tables where table_schema= ? and table_name= ?";
        List<Table> list = JdbcUtil.executeQuery(connection, tableQuerySql, Table.class, config.getName(), config.getTableName());
        Table table = list.get(0);
        System.out.println(table);

        String columnQuerySql = "select column_name,column_type,column_key,ifnull(column_comment,'--') column_comment from information_schema.columns where table_schema= ? and table_name= ?";
        List<Field> fields = JdbcUtil.executeQuery(connection, columnQuerySql, Field.class, config.getName(), config.getTableName());
//        ResultSet columnResultSet = JdbcUtil.executeQuery(connection, columnQuerySql, config.getName(), config.getTableName());
//        List<Field> fields = new ArrayList<>();
//        Field field;
//        while (columnResultSet.next()) {
//            field = new Field();
//            field.setName(columnResultSet.getString("column_name"));
//            field.setType(columnResultSet.getString("column_type"));
//            field.setKey(columnResultSet.getString("column_key"));
//            field.setComment(columnResultSet.getString("column_comment"));
//
//            fields.add(field);
//        }

        table.setPrefix(config.getPrefix());
        table.setFields(fields);
        logger.info("生成代码字段：【{}】", fields);
        logger.info("模板代码生成的文件目录：【{}】", outputDir);

        // 使用velocity模板
        VelocityEngine velocityEngine = new VelocityEngine();
        velocityEngine.setProperty("resource.loader.file.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
        velocityEngine.init();

        // 创建上下文并填充数据
        VelocityContext context = new VelocityContext();
        context.put("table", table);
        Map<String, String> pkg = new HashMap<>();
        pkg.put("Entity", entity);
        pkg.put("Bo", bo);
        pkg.put("Mapper", mapper);
        pkg.put("Service", service);
        pkg.put("Controller", controller);
        context.put("package", pkg);
        context.put("author", author);
        context.put("date", Ft.Date.formatDate(new Date(), Ft.Date.DATE_TIME_FORMAT_6));

        // 写入文件，Entity实体类
        StringWriter writer = new StringWriter();
        Template template = velocityEngine.getTemplate("tpl/mybatis/Entity.java.vm");
        template.merge(context, writer);
        logger.debug("生成Entity模板代码：\n{}", writer);
        // 先判断目录是否存在，不存在创建
        File file = new File(outputDir + "/src/main/java/" + entity.replace(".", "/") + "/");
        if (!file.exists()) {
            file.mkdirs();
        }
        // 输出到新文件
        try (FileWriter fileWriter = new FileWriter(outputDir + "/src/main/java/" + entity.replace(".", "/") + "/" + table.getClazz() + ".java")) {
            fileWriter.write(writer.toString());
        } catch (IOException e) {
            logger.error("Entity文件写入失败！", e);
        }

        writer = new StringWriter();
        template = velocityEngine.getTemplate("tpl/mybatis/Bo.java.vm");
        template.merge(context, writer);
        logger.debug("生成Bo模板代码：\n{}", writer);
        // 先判断目录是否存在，不存在创建
        file = new File(outputDir + "/src/main/java/" + bo.replace(".", "/") + "/");
        if (!file.exists()) {
            file.mkdirs();
        }
        // 输出到新文件
        try (FileWriter fileWriter = new FileWriter(outputDir + "/src/main/java/" + bo.replace(".", "/") + "/" + table.getClazz() + "Bo.java")) {
            fileWriter.write(writer.toString());
        } catch (IOException e) {
            logger.error("Example文件写入失败！", e);
        }

        writer = new StringWriter();
        template = velocityEngine.getTemplate("tpl/mybatis/Example.java.vm");
        template.merge(context, writer);
        logger.debug("生成Example模板代码：\n{}", writer);
        // 输出到新文件
        try (FileWriter fileWriter = new FileWriter(outputDir + "/src/main/java/" + entity.replace(".", "/") + "/" + table.getClazz() + "Example.java")) {
            fileWriter.write(writer.toString());
        } catch (IOException e) {
            logger.error("Example文件写入失败！", e);
        }

        writer = new StringWriter();
        template = velocityEngine.getTemplate("tpl/mybatis/Mapper.java.vm");
        template.merge(context, writer);
        logger.debug("生成Mapper模板代码：\n{}", writer);
        // 先判断目录是否存在，不存在创建
        file = new File(outputDir + "/src/main/java/" + mapper.replace(".", "/") + "/");
        if (!file.exists()) {
            file.mkdirs();
        }
        // 输出到新文件
        try (FileWriter fileWriter = new FileWriter(outputDir + "/src/main/java/" + mapper.replace(".", "/") + "/" + table.getClazz() + "Mapper.java")) {
            fileWriter.write(writer.toString());
        } catch (IOException e) {
            logger.error("Mapper文件写入失败！", e);
        }

        writer = new StringWriter();
        template = velocityEngine.getTemplate("tpl/mybatis/Mapper.xml.vm");
        template.merge(context, writer);
        logger.debug("生成Xml模板代码：\n{}", writer);
        // 先判断目录是否存在，不存在创建
        file = new File(outputDir + "/src/main/resources/mapper/" + mapperXml);
        if (!file.exists()) {
            file.mkdirs();
        }
        // 输出到新文件
        try (FileWriter fileWriter = new FileWriter(outputDir + "/src/main/resources/mapper/" + mapperXml + table.getClazz() + "Mapper.xml")) {
            fileWriter.write(writer.toString());
        } catch (IOException e) {
            logger.error("Xml文件写入失败！", e);
        }

        writer = new StringWriter();
        template = velocityEngine.getTemplate("tpl/mybatis/Service.java.vm");
        template.merge(context, writer);
        logger.debug("生成Service模板代码：\n{}", writer);
        // 先判断目录是否存在，不存在创建
        file = new File(outputDir + "/src/main/java/" + service.replace(".", "/") + "/");
        if (!file.exists()) {
            file.mkdirs();
        }
        // 输出到新文件
        try (FileWriter fileWriter = new FileWriter(outputDir + "/src/main/java/" + service.replace(".", "/") + "/" + table.getClazz() + "Service.java")) {
            fileWriter.write(writer.toString());
        } catch (IOException e) {
            logger.error("Service文件写入失败！", e);
        }

        writer = new StringWriter();
        template = velocityEngine.getTemplate("tpl/mybatis/Controller.java.vm");
        template.merge(context, writer);
        logger.debug("生成Controller模板代码：\n{}", writer);
        // 先判断目录是否存在，不存在创建
        file = new File(outputDir + "/src/main/java/" + controller.replace(".", "/") + "/");
        if (!file.exists()) {
            file.mkdirs();
        }
        // 输出到新文件
        try (FileWriter fileWriter = new FileWriter(outputDir + "/src/main/java/" + controller.replace(".", "/") + "/" + table.getClazz() + "Controller.java")) {
            fileWriter.write(writer.toString());
        } catch (IOException e) {
            logger.error("Controller文件写入失败！", e);
        }

        writer = new StringWriter();
        template = velocityEngine.getTemplate("tpl/mybatis/api.js.vm");
        template.merge(context, writer);
        logger.debug("生成api.js模板代码：\n{}", writer);
        // 先判断目录是否存在，不存在创建
        file = new File(outputDir + "/src/front/");
        if (!file.exists()) {
            file.mkdirs();
        }
        // 输出到新文件
        try (FileWriter fileWriter = new FileWriter(outputDir + "/src/front/api.js")) {
            fileWriter.write(writer.toString());
        } catch (IOException e) {
            logger.error("api.js文件写入失败！", e);
        }

        writer = new StringWriter();
        template = velocityEngine.getTemplate("tpl/mybatis/index.vue.vm");
        template.merge(context, writer);
        logger.debug("生成index.vue模板代码：\n{}", writer);
        // 先判断目录是否存在，不存在创建
        file = new File(outputDir + "/src/front/");
        if (!file.exists()) {
            file.mkdirs();
        }
        // 输出到新文件
        try (FileWriter fileWriter = new FileWriter(outputDir + "/src/front/index.vue")) {
            fileWriter.write(writer.toString());
        } catch (IOException e) {
            logger.error("index.vue文件写入失败！", e);
        }

        writer = new StringWriter();
        template = velocityEngine.getTemplate("tpl/mybatis/detail.vue.vm");
        template.merge(context, writer);
        logger.debug("生成detail.vue模板代码：\n{}", writer);
        // 先判断目录是否存在，不存在创建
        file = new File(outputDir + "/src/front/");
        if (!file.exists()) {
            file.mkdirs();
        }
        // 输出到新文件
        try (FileWriter fileWriter = new FileWriter(outputDir + "/src/front/detail.vue")) {
            fileWriter.write(writer.toString());
        } catch (IOException e) {
            logger.error("detail.vue文件写入失败！", e);
        }

        JdbcUtil.closeConnect();
    }

    private static String getUrl(GenerateConfig config) {
        return String.format(jdbcUrl, config.getHost(), config.getPort(), config.getName());
    }

    /**
     * 读取文件
     *
     * @param config
     * @return
     */
    public Map<String, String> readFile(GenerateConfig config) {
        // 获取当前工作目录
        String userDir = System.getProperty("user.dir");
        // 使用 Paths.get 方法拼接路径
        Path targetPath = Paths.get(userDir, "src");
        // 获取拼接后的路径字符串
        logger.info("读取代码文件路径【{}】", targetPath.toString());
        Map<String, String> map = new HashMap<>();
        readFile(targetPath, map, config);
        return map;
    }

    private static void readFile(Path path, Map<String, String> map, GenerateConfig config) {
        File folder = new File(path.toUri());
        File[] files = folder.listFiles();
        String clazz;
        if (Ft.String.isBlank(config.getPrefix())) {
            clazz = Ft.String.underlineToHeadUpper(config.getTableName());
        } else {
            clazz = Ft.String.underlineToHeadUpper(config.getTableName().replace(config.getPrefix(), ""));
        }
        for (File file : files) {
            if (file.isFile() && (file.getName().startsWith(clazz) || "api.js".equals(file.getName()) || "index.vue".equals(file.getName()) || "detail.vue".equals(file.getName()))) {
                StringBuilder contentBuilder = new StringBuilder();
                try (Stream<String> stream = Files.lines(file.toPath(), StandardCharsets.UTF_8)) {
                    stream.forEach(s -> contentBuilder.append(s).append("\n"));
                    map.put(file.getName(), contentBuilder.toString());
                } catch (IOException e) {
                    logger.error("读取文件失败！", e);
                    throw new BizException("读取文件失败！");
                }
            } else if (file.isDirectory()) {
                readFile(file.toPath(), map, config);
            }
        }

    }
}
